<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script>
        (function () {
            // 闭包是有权访问另一个函数作用域中的变量和函数（活动对象），
            // 创建闭包的常见方式就是在一个函数内部创建另一个函数


            function compare(value1, value2) {
                if (value1 < value2) {
                    return -1;
                } else if (value1 > value2) {
                    return 1;
                } else {
                    return 0;
                }
            }

            // 当调用函数的时候,会创建一个执行环境和作用域链
            // 每一个执行环境中都有一个表示变量的对象———变量对象
            // 使用arguments和命名参数的值来初始化本函数的活动对象
            // 这里的arguments 是[1,2]，命名参数分别是 1,2；
            // 在本函数的作用域链中 第一位是本函数的活动对象
            // 第二位是外部函数的活动对象
            // 第三位是外部函数的外部函数的活动对象
            // 一直到做作用域的终点的全局执行环境，包含全局变量和函数，

            // 因此在函数执行过程中，为读取和写入变量的值，就必须从作用域链中去寻找相应的值
            // 当调用compare()时会创建一个包含arguments 和 value1、value2的活动对象在其作用域链的第一层
            // 而全局执行环境的变量对象在其作用域的第二层

            // 全局环境中的变量对象始终存在，而像compare()函数这样的局部环境的变量对象，
            // 只在函数执行过程中存在，函数执行完毕后，变量对象就销毁了，所以外部的函数是不能访问到局部环境中的变量对象
            // 作用域链本质上是一个指向变量对象的指针列表，它只是引用但不实际包含变量对象，
            // 所以当函数执行完毕变量对象销毁了，当然作用域中就不存在对此变量对象的引用了
            var result = compare(1, 2);
            // 函数执行完毕 变量对象销毁了 作用域链中就不能访问到compare()内部的变量对象了
            // 执行 console.log(value1) 会报错
            console.log(result);

            let a = [1, 3, 45, 89, 32];
            a.sort(compare);
            console.log(a);
        })();
        (function () {
            // 一般来讲当函数执行完毕后局部活动对象就会被销毁，内存中仅保存全局作用域(全局执行环境的变量对象)
            // 但是，闭包的情况又有所不同

            // 在compare1 函数内部定义的匿名函数的作用域链中包含着外部函数compare1的活动对象
            function compare1(param) {
                return function (obj1, obj2) {
                    // 作用域链中引用着外部函数的活动对象 param 因此函数返回执行过程中能使用外部函数的变量对象
                    var value1 = obj1[param];
                    var value2 = obj2[param];
                    if (value1 < value2) {
                        return -1;
                    } else if (value1 > value2) {
                        return 1;
                    } else {
                        return 0;
                    }
                }
            }

            // 当匿名函数被compare1()执行返回后，匿名函数的作用域被初始化为包含compare1()函数的活动对象和全局变量对象
            // 这样匿名函数就能访问compare1()函数中的变量对象，更为重要的是当compare1()执行结束后，其活动对象也不会销毁
            // 因为匿名函数仍在引用着它的活动对象，compare1()的执行环境中的作用域链虽然被销毁了，但其活动对象仍在匿名函数中引用着
            // 直到匿名函数被销毁后，compare1()中的活动对象才能真正从内存中释放销毁
            var compare = compare1('name');

            // 当执行匿名函数时 它的作用域链被初始为包含arguments，obj1，obj2值的活动对象且在其作用域链的第一层
            // 第二层是引用外部compare1()的arguments、param值的活动对象
            // 第三层是全局变量对象
            var result = compare({
                name: "Nichloas"
            }, {
                name: "Greg"
            });

            // 解除匿名函数的引用，以便释放内存，这样外部函数的活动对象也真正从内存中销毁
            compare = null;

            console.log(result);

            // 由于闭包会携带包含它的函数的作用域，因此会比其他函数占用更多的内存
            // 过渡使用闭包会造成内存占用过多
            // 在必要的时候才使用闭包，
            // 而且尽量减少作用域链的层级数，把全局作用域上或外部函数上的变量对象保存在局部环境中，提高性能
        })();

        (function () {
            // 作用域链的这种配置机制引出了一个值得注意的副作用，
            // 即闭包只能取得包含函数中任何变量对象的最后一个值，
            // 别忘了闭包保存时整个变量对象，而不是某个特殊的值，因此只包含最终变化后的值
            
           
            function createFunctions(){
                var result=[];

                for (var index = 0; index < 10; index++) {
                        result[index]=function(){
                            console.log(index);
                        }             
                }
                return result;
            }

            var array=createFunctions();
            
            // 全部返回的是10
            // 因为在result[i]执行的过程中 这里的console.log(index)中的index 实际上是外部createFunctions
            // 中的活动对象，虽然createFunctions()执行完毕作用域链销毁，但其活动对象仍然被result[i]引用着
            // 而结合闭包只能取得包含函数中的任何变量的最后一个变化后的值，所以当执行result[i]时
            // for循环已经结束，index的值已经为10  最终console.log(index) 只能全部输出10
            // 使用ES5会产生这种问题，而ES6使用let 有了块级作用域 就不会有这种情况
            for (let index = 0; index < array.length; index++) {
                const element = array[index];
                element();//10,10,....
            }

            // 但反过来也正好利用闭包的特性——保存外部函数的变量对象，
            // 可以在内部函数再创建一个函数，再形成一个闭包，利用其引用一开始的变量值
            // 在最内部函数调用时引用的变量值实际上是其外部函数作用域链上在createFunctions1上引用的变量对象
            // 就会使这个index值达到我们想要的效果
            
            function createFunctions1(){
                var result=[];

                for (var index = 0; index < 10; index++) {
                    // 函数内部再创建一个函数，形成闭包，立即执行外部函数将index传入，这样一来
                    // 在内部函数就引用着这个index是最初的index值
                        result[index]=function(param){
                            return function(){
                                console.log(param);
                            }
                        }(index);          
                }
                return result;
            }
            
            var array1=createFunctions1();
            for (let index = 0; index < array1.length; index++) {
                const element = array1[index];
                element();//0,1,2,...9
            }

            // 最后要解除这个匿名函数的引用，释放内存
            array1=null;
        })();
    </script>
</body>

</html>